package com.xxl.job.executor.common.util.http;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.*;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultHttpResponseParserFactory;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import org.springframework.beans.factory.annotation.Value;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

/**
 * @description：连接初始化管理类
 * @author: huaruan
 * @DATE: 2020-03-14 10:17
 * @projectName: hgsoft
 */
public class HttpsInitManager {
    private static Log logger = LogFactory.getLog(HttpsInitManager.class);

//    @Value("#{config['httpsSocketTimeout']?:60000}")
    int socketTimeout = 60000;//socket超时时间，单位毫秒
//    @Value("#{config['httpsConnTimeout']?:60000}")
    int connectTimeout = 60000;//连接超时时间，单位毫秒

    boolean needCRT = false;
    private String keyStorePath = "";
    private String keyStorePass = "";

    private boolean needProxyServer = false;
    private String proxyServerIp = "127.0.0.1";
    private Integer proxyServerPort = 80;

    private SSLContext sslContext;
    public void init() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
        if (needCRT) {
            sslContext = custom(keyStorePath, keyStorePass);
        } else {
            sslContext = new SSLContextBuilder().useProtocol("TLSv1.2").loadTrustMaterial(null, new TrustStrategy() {
                // 默认信任所有证书
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
        }
        if (needHttpsPool) {
            initByPool();
        }
    }

    /**
     * 效率会比较慢，每一次都会创建并关闭，但不会出现线程不断增加的问题
     * @return
     */
    public CloseableHttpClient getHttpsClient(){
        HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,hostnameVerifier);

        SocketConfig socketConfig = SocketConfig.custom()
                .setSoKeepAlive(true)
                .setTcpNoDelay(true)
                .setSoReuseAddress(true)
                .build();
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout).build();

        HttpClientBuilder clientBuilder = HttpClientBuilder.create();
        if (needProxyServer) {
            HttpHost proxy = new HttpHost(proxyServerIp, proxyServerPort);
            return clientBuilder
                    .setSSLSocketFactory(sslsf)
                    .setDefaultRequestConfig(requestConfig)
                    .setProxy(proxy)
                    .setDefaultSocketConfig(socketConfig).build();
        }
        return clientBuilder
                .setSSLSocketFactory(sslsf)
                .setDefaultRequestConfig(requestConfig)
                .setDefaultSocketConfig(socketConfig).build();
    }

    /**
     * 设置信任自签名证书
     *
     * @param keyStorePath      密钥库路径
     * @param keyStorepass      密钥库密码
     * @return
     */
    public static SSLContext custom(String keyStorePath, String keyStorepass) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
        SSLContext sc = null;
        FileInputStream instream = null;
        KeyStore trustStore = null;
        try {
            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            instream = new FileInputStream(new File(keyStorePath));
            trustStore.load(instream, keyStorepass.toCharArray());
            // 相信自己的CA和所有自签名的证书
            sc = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
            instream.close();
        }  catch (Exception e) {
            logger.error("设置信任自签名证书出错", e);
            throw e;
        }
        finally {
        	if(instream !=null) {
        		instream.close();
        	}
        }
        return sc;
    }

    public int getSocketTimeout() {
        return socketTimeout;
    }

    public void setSocketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public boolean isNeedCRT() {
        return needCRT;
    }

    public void setNeedCRT(boolean needCRT) {
        this.needCRT = needCRT;
    }

    public String getKeyStorePath() {
        return keyStorePath;
    }

    public void setKeyStorePath(String keyStorePath) {
        this.keyStorePath = keyStorePath;
    }

    public String getKeyStorePass() {
        return keyStorePass;
    }

    public void setKeyStorePass(String keyStorePass) {
        this.keyStorePass = keyStorePass;
    }

    public boolean isNeedProxyServer() {
        return needProxyServer;
    }

    public void setNeedProxyServer(boolean needProxyServer) {
        this.needProxyServer = needProxyServer;
    }

    public String getProxyServerIp() {
        return proxyServerIp;
    }

    public void setProxyServerIp(String proxyServerIp) {
        this.proxyServerIp = proxyServerIp;
    }

    public Integer getProxyServerPort() {
        return proxyServerPort;
    }

    public void setProxyServerPort(Integer proxyServerPort) {
        this.proxyServerPort = proxyServerPort;
    }

    /**
     * 使用连接池
     */
    PoolingHttpClientConnectionManager poolConnManager;
    int maxTotalPool = 200;
    int maxConPerRoute = 200;
    int connectionRequestTimeout = 60000;
    int validateAfterInactivity=5000;
    long evictIdleConnections=60;
    long connectionTimeToLive=60;
    boolean needHttpsPool = true;

    private void initByPool() {
        //如果不设置这个hostnameVerifier ；
        // 就会报Host name 'sdts.txffp.com' does not match the certificate subject provided by the peer错误
        HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,hostnameVerifier);

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslsf)
                .build();

        HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> httpConnectionFactory = new ManagedHttpClientConnectionFactory(
                DefaultHttpRequestWriterFactory.INSTANCE, DefaultHttpResponseParserFactory.INSTANCE);

        DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;

        poolConnManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, httpConnectionFactory, dnsResolver);

        SocketConfig defaultSocketConfig = SocketConfig.custom()
                .setTcpNoDelay(true)
                .setSoKeepAlive(true)
                .setSoReuseAddress(true)
                .build();

        poolConnManager.setDefaultSocketConfig(defaultSocketConfig);

        // Increase max total connection to 200
        poolConnManager.setMaxTotal(maxTotalPool);
        // Increase default max connection per route to 50
        poolConnManager.setDefaultMaxPerRoute(maxConPerRoute);
        // 从连接池获取连接时，连接不活跃多久需要进行一次检查
        poolConnManager.setValidateAfterInactivity(validateAfterInactivity);
    }

    /**
     * 会导致线程数不断增加，如果将HttpClient必做浏览器，那么就会生成无数的浏览器，并且不会关闭
     * @return
     */
    @Deprecated
    public CloseableHttpClient getHttpsClientByPool() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout).build();

        if (needProxyServer) {
            HttpHost proxy = new HttpHost(proxyServerIp, proxyServerPort);
            CloseableHttpClient httpClient = HttpClients.custom()
                    .setConnectionManager(poolConnManager)
                    .setDefaultRequestConfig(requestConfig)
                    .setProxy(proxy)
                    .setConnectionManager(poolConnManager).setConnectionManagerShared(false)
                    // 定时回收空闲连接
                    .evictIdleConnections(evictIdleConnections, TimeUnit.SECONDS)
                    // 定期回收过期连接
                    .evictExpiredConnections()
                    // 连接存活时间
                    .setConnectionTimeToLive(connectionTimeToLive, TimeUnit.SECONDS)
                    .setDefaultRequestConfig(requestConfig)
                    .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                    .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE).build();
            if (poolConnManager != null && poolConnManager.getTotalStats() != null) {
                logger.info("传输子系统连接池状态：" + poolConnManager.getTotalStats().toString());
            }
            return httpClient;
        }
        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(poolConnManager)
                .setDefaultRequestConfig(requestConfig)

                .setConnectionManager(poolConnManager).setConnectionManagerShared(false)
                // 定时回收空闲连接
                .evictIdleConnections(evictIdleConnections, TimeUnit.SECONDS)
                // 定期回收过期连接
                .evictExpiredConnections()
                // 连接存活时间
                .setConnectionTimeToLive(connectionTimeToLive, TimeUnit.SECONDS)
                .setDefaultRequestConfig(requestConfig)
                .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE).build();
        if (poolConnManager != null && poolConnManager.getTotalStats() != null) {
            logger.info("传输子系统连接池状态：" + poolConnManager.getTotalStats().toString());
        }
        return httpClient;
    }

    public int getMaxTotalPool() {
        return maxTotalPool;
    }

    public void setMaxTotalPool(int maxTotalPool) {
        this.maxTotalPool = maxTotalPool;
    }

    public int getMaxConPerRoute() {
        return maxConPerRoute;
    }

    public void setMaxConPerRoute(int maxConPerRoute) {
        this.maxConPerRoute = maxConPerRoute;
    }

    public int getConnectionRequestTimeout() {
        return connectionRequestTimeout;
    }

    public void setConnectionRequestTimeout(int connectionRequestTimeout) {
        this.connectionRequestTimeout = connectionRequestTimeout;
    }

    public int getValidateAfterInactivity() {
        return validateAfterInactivity;
    }

    public void setValidateAfterInactivity(int validateAfterInactivity) {
        this.validateAfterInactivity = validateAfterInactivity;
    }

    public long getEvictIdleConnections() {
        return evictIdleConnections;
    }

    public void setEvictIdleConnections(long evictIdleConnections) {
        this.evictIdleConnections = evictIdleConnections;
    }

    public long getConnectionTimeToLive() {
        return connectionTimeToLive;
    }

    public void setConnectionTimeToLive(long connectionTimeToLive) {
        this.connectionTimeToLive = connectionTimeToLive;
    }

    public boolean isNeedHttpsPool() {
        return needHttpsPool;
    }

    public void setNeedHttpsPool(boolean needHttpsPool) {
        this.needHttpsPool = needHttpsPool;
    }


    /**
     * 单个httpclient,解决线程不断怎么多的问题
     */
    private CloseableHttpClient httpClient;
    private boolean singleClient = true;

    public CloseableHttpClient getHttpsClientByPoolNew() {

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout).build();

        if(httpClient == null){
            synchronized (HttpsInitManager.class){
                if(httpClient == null){
                    if (needProxyServer) {
                        HttpHost proxy = new HttpHost(proxyServerIp, proxyServerPort);
                        httpClient = HttpClients.custom()
                                .setConnectionManager(poolConnManager)
                                .setDefaultRequestConfig(requestConfig)
                                .setProxy(proxy)
                                .setConnectionManager(poolConnManager).setConnectionManagerShared(false)
                                // 定时回收空闲连接
                                .evictIdleConnections(evictIdleConnections, TimeUnit.SECONDS)
                                // 定期回收过期连接
                                .evictExpiredConnections()
                                // 连接存活时间
                                .setConnectionTimeToLive(connectionTimeToLive, TimeUnit.SECONDS)
                                .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                                .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE).build();
                        if (poolConnManager != null && poolConnManager.getTotalStats() != null) {
                            logger.info("传输子系统连接池状态：" + poolConnManager.getTotalStats().toString());
                        }

                    }else{
                        httpClient = HttpClients.custom()
                                .setConnectionManager(poolConnManager)
                                .setDefaultRequestConfig(requestConfig)

                                .setConnectionManager(poolConnManager).setConnectionManagerShared(false)
                                // 定时回收空闲连接
                                .evictIdleConnections(evictIdleConnections, TimeUnit.SECONDS)
                                // 定期回收过期连接
                                .evictExpiredConnections()
                                // 连接存活时间
                                .setConnectionTimeToLive(connectionTimeToLive, TimeUnit.SECONDS)
                                .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                                .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE).build();
                        if (poolConnManager != null && poolConnManager.getTotalStats() != null) {
                            logger.info("传输子系统连接池状态：" + poolConnManager.getTotalStats().toString());
                        }
                    }

                    Runtime.getRuntime().addShutdownHook(new Thread() {
                        @Override
                        public void run() {
                            try {
                                if(httpClient != null){
                                    httpClient.close();
                                    logger.info("关闭http连接");
                                }

                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }
        return httpClient;
    }

    public boolean isSingleClient() {
        return singleClient;
    }

    public void setSingleClient(boolean singleClient) {
        this.singleClient = singleClient;
    }
}
